package cloud.tianai.mate.captcha.generator.impl;

import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.model.dto.*;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import cloud.tianai.mate.captcha.common.constant.MateCaptchaTypeConstant;
import cloud.tianai.mate.captcha.generator.common.model.dto.CurveImageCaptchaInfo;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @Author: 天爱有情
 * @date 2024/3/16 11:38
 * @Description 曲线匹配验证码
 */
public class MateSliderCurveImageCaptchaGenerator extends AbstractImageCaptchaGenerator {


    public MateSliderCurveImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager) {
        super(imageCaptchaResourceManager);
    }


    public MateSliderCurveImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, CaptchaInterceptor interceptor) {
        super(imageCaptchaResourceManager);
        setImageTransform(imageTransform);
        setInterceptor(interceptor);
    }
    @Override
    protected void doInit() {
//        ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
//        // 添加一些系统的资源文件
//        resourceStore.addResource(MateCaptchaTypeConstant.CURVE, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"), DEFAULT_TAG));
    }

    @Override
    protected void doGenerateCaptchaImage(CaptchaExchange captchaExchange) {
        GenerateParam param = captchaExchange.getParam();
        Resource bgResource = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag());
        BufferedImage bgImage = getResourceImage(bgResource);
        int bgImageWidth = bgImage.getWidth();
        int bgImageHeight = bgImage.getHeight();
        ThreadLocalRandom random = ThreadLocalRandom.current();
        // 起始点 20 ~ 60 之间 , 暂时写死
        int startX = random.nextInt(20, bgImageWidth / 4);
        int startY = random.nextInt(20, 60);
        // 结束点 20 ~ 60 之间 , 暂时写死
        int endX = random.nextInt(bgImageWidth - bgImageWidth / 4, bgImageWidth - 20);
        int endY = random.nextInt(20, 60);
        // 控制点x 预留20左右的空间
        int ctrlX = random.nextInt(bgImageWidth / 6, bgImageWidth - bgImageWidth / 6);
        // 控制点y写死
        int ctrlY = random.nextInt(bgImageHeight - bgImageHeight / 2, bgImageHeight + bgImageHeight / 6);
        Graphics2D g2d = bgImage.createGraphics();
        drawBezier(g2d, startX, startY, endX, endY, ctrlX, ctrlY);

        if (param.getObfuscate()) {
            int obfuscateCtrlX = random.nextInt(bgImageWidth / 6, bgImageWidth);
            int obfuscateCtrlY;
            if (ctrlY + bgImageHeight / 6 >= bgImageHeight + bgImageHeight / 6) {
                obfuscateCtrlY = random.nextInt(bgImageHeight - bgImageHeight / 2, ctrlY - 50);
            } else {
                obfuscateCtrlY = random.nextInt(ctrlY + bgImageHeight / 6, bgImageHeight + bgImageHeight / 6);

            }
            drawBezier(g2d, startX, startY, endX, endY, obfuscateCtrlX, obfuscateCtrlY);
        }


        // 关闭Graphics2D上下文
        g2d.dispose();


        CurveData curveData = new CurveData();
        curveData.startX = startX;
        curveData.startY = startY;
        curveData.endX = endX;
        curveData.endY = endY;
        curveData.ctrlX = ctrlX;
        curveData.ctrlY = ctrlY;
        captchaExchange.setTransferData(curveData);
        captchaExchange.setBackgroundImage(bgImage);
        captchaExchange.setResourceImage(bgResource);
    }


    /**
     * 画贝塞尔曲线， 单独抽离，用于后期扩展
     *
     * @param g2d    g2d
     * @param startX 起始点X
     * @param startY 起始点T
     * @param endX   结束点X
     * @param endY   结束点Y
     * @param ctrlX  控制点X
     * @param ctrlY  控制点Y
     */
    public void drawBezier(Graphics2D g2d, double startX, double startY, double endX, double endY, double ctrlX, double ctrlY) {
        // 设置曲线的颜色
//        g2d.setColor(new Color(180, 177, 177, 107));
//        // 设置边框颜色和宽度
//        g2d.setStroke(new BasicStroke(20f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        ThreadLocalRandom random = ThreadLocalRandom.current();
        // 随机渐变
        Color color1 = new Color(random.nextInt(255),
                random.nextInt(255),
                random.nextInt(255),
                random.nextInt(100,150));
        Color color2 = new Color(random.nextInt(255),
                random.nextInt(255),
                random.nextInt(255),
                random.nextInt(100,150));

        Point2D.Double start = new Point2D.Double(startX, startY);
        Point2D.Double end = new Point2D.Double(endX, endY);
        g2d.setPaint(new GradientPaint(start, color1, end, color2));
//        g2d.setStroke(new BasicStroke(20f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));

//        g2d.setStroke( new CompositeStroke( new BasicStroke( 20f ), new BasicStroke( 1f ) ) );
        g2d.setStroke(new WobbleStroke(random.nextInt(1,8),random.nextInt(3,8)));
        Path2D path = new Path2D.Double();
        // 开始构建路径，从起点开始
        path.moveTo(startX, startY);
        // 添加三次贝塞尔曲线段
        path.curveTo(ctrlX, ctrlY, ctrlX, ctrlY, endX, endY);
        // 绘制路径
        g2d.draw(path);
    }


    @Override
    protected ImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaExchange captchaExchange) {
        GenerateParam param = captchaExchange.getParam();
        BufferedImage backgroundImage = captchaExchange.getBackgroundImage();
        int backgroundImageWidth = backgroundImage.getWidth();
        int backgroundImageHeight = backgroundImage.getHeight();
        Resource resourceImage = captchaExchange.getResourceImage();
        CustomData customData = captchaExchange.getCustomData();
        CurveData curveData = (CurveData) captchaExchange.getTransferData();
        ImageTransformData transform = getImageTransform().transform(param, backgroundImage, resourceImage, customData);
        CurveImageCaptchaInfo curveImageCaptchaInfo = new CurveImageCaptchaInfo();
        curveImageCaptchaInfo.setStartX(curveData.startX);
        curveImageCaptchaInfo.setStartY(curveData.startY);
        curveImageCaptchaInfo.setEndX(curveData.endX);
        curveImageCaptchaInfo.setEndY(curveData.endY);
        curveImageCaptchaInfo.setCtrlX(curveData.ctrlX);
        curveImageCaptchaInfo.setCtrlY(curveData.ctrlY);
        curveImageCaptchaInfo.setRandomX(curveData.ctrlX);
        curveImageCaptchaInfo.setBackgroundImage(transform.getBackgroundImageUrl());
        curveImageCaptchaInfo.setBackgroundImageTag(resourceImage.getTag());
        curveImageCaptchaInfo.setBackgroundImageWidth(backgroundImage.getWidth());
        curveImageCaptchaInfo.setBackgroundImageHeight(backgroundImage.getHeight());
        curveImageCaptchaInfo.setType(MateCaptchaTypeConstant.CURVE);
        double leftX = (double) curveData.startX / backgroundImageWidth;
        double leftY = (double) curveData.startY / backgroundImageHeight;
        double rightX = (double) curveData.endX / backgroundImageWidth;
        double rightY = (double) curveData.endY / backgroundImageHeight;
        double bottomY = (double) curveData.ctrlY / backgroundImageHeight;
        List<Double> points = Arrays.asList(leftX, leftY, rightX, rightY, bottomY);
        List<Integer> pointsOrder = Arrays.asList(0, 1, 2, 3, 4);
        Collections.shuffle(pointsOrder);
        StringBuilder sb = new StringBuilder();
        for (Integer index : pointsOrder) {
            sb.append(points.get(index));
        }
        for (int i = 0; i < pointsOrder.size(); i++) {
            sb.insert((int) Math.pow(2, pointsOrder.size() - i - 1), pointsOrder.get(i));
        }
        customData.putViewData("p", sb);
        // 曲线容错率大一点点
        curveImageCaptchaInfo.setTolerant(0.05f);
        return curveImageCaptchaInfo;
    }

    public static class CurveData {
        int startX;
        int startY;
        int endX;
        int endY;
        int ctrlX;
        int ctrlY;

    }

    public static class CompositeStroke implements Stroke {
        private Stroke stroke1, stroke2;

        public CompositeStroke(Stroke stroke1, Stroke stroke2 ) {
            this.stroke1 = stroke1;
            this.stroke2 = stroke2;
        }

        @Override
        public Shape createStrokedShape( Shape shape ) {
            return stroke2.createStrokedShape( stroke1.createStrokedShape( shape ) );
        }
    }

    public class WobbleStroke implements Stroke {
        private float detail = 2;
        private float amplitude = 2;
        private static final float FLATNESS = 1;

        public WobbleStroke( float detail, float amplitude ) {
            this.detail	= detail;
            this.amplitude	= amplitude;
        }

        @Override
        public Shape createStrokedShape( Shape shape ) {
            GeneralPath result = new GeneralPath();
            shape = new BasicStroke( 10 ).createStrokedShape( shape );
            PathIterator it = new FlatteningPathIterator( shape.getPathIterator( null ), FLATNESS );
            float points[] = new float[6];
            float moveX = 0, moveY = 0;
            float lastX = 0, lastY = 0;
            float thisX = 0, thisY = 0;
            int type = 0;
            boolean first = false;
            float next = 0;

            while ( !it.isDone() ) {
                type = it.currentSegment( points );
                switch( type ){
                    case PathIterator.SEG_MOVETO:
                        moveX = lastX = randomize( points[0] );
                        moveY = lastY = randomize( points[1] );
                        result.moveTo( moveX, moveY );
                        first = true;
                        next = 0;
                        break;

                    case PathIterator.SEG_CLOSE:
                        points[0] = moveX;
                        points[1] = moveY;
                        // Fall into....

                    case PathIterator.SEG_LINETO:
                        thisX = randomize( points[0] );
                        thisY = randomize( points[1] );
                        float dx = thisX-lastX;
                        float dy = thisY-lastY;
                        float distance = (float)Math.sqrt( dx*dx + dy*dy );
                        if ( distance >= next ) {
                            float r = 1.0f/distance;
                            float angle = (float)Math.atan2( dy, dx );
                            while ( distance >= next ) {
                                float x = lastX + next*dx*r;
                                float y = lastY + next*dy*r;
                                result.lineTo( randomize( x ), randomize( y ) );
                                next += detail;
                            }
                        }
                        next -= distance;
                        first = false;
                        lastX = thisX;
                        lastY = thisY;
                        break;
                }
                it.next();
            }

            return result;
        }

        private float randomize( float x ) {
            return x+(float)Math.random()*amplitude*2-1;
        }

    }
}
